#ifndef CORE_GOOGLE_BUFFER_PROTO_TRANSATOR_H_INCLUDED
#define CORE_GOOGLE_BUFFER_PROTO_TRANSATOR_H_INCLUDED

#include <string>
#include <vector>
#include <cstdio>
#include <boost/cstdint.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/noncopyable.hpp>

namespace Core { namespace Proto {

/**
 * The GoogleBufferProtoTranslator
 */
static std::size_t const HEADER_SIZE = 4;
typedef std::vector<boost::uint8_t> BufferType;	

template <class MessType>
class GoogleBufferProtoTranslator : boost::noncopyable 
{
public:	
	typedef MessType MessageType;
	typedef boost::shared_ptr<MessType> MessagePtr;

    GoogleBufferProtoTranslator(MessagePtr msg = MessagePtr())
        : m_msg(msg)
    { }

	inline void setMessage(MessagePtr msg)
		{ m_msg = msg; }

	inline  MessagePtr getMessage()
    	{ return m_msg; }

    bool encodePack(BufferType & buf) const
    {
        if (!m_msg)
            return false;

        unsigned size = m_msg->ByteSize();
        buf.resize(HEADER_SIZE + size);
        encodeHeader(buf, size);
        return m_msg->SerializeToArray(&buf[HEADER_SIZE], size);
    }

    unsigned translateHeader(BufferType const & buf) const
    {
        if (buf.size() < HEADER_SIZE)
            return 0;

        unsigned size = 0;
        for (unsigned i = 0; i < HEADER_SIZE; ++i)
            size = size * 256 + (static_cast<unsigned>(buf[i]) & 0xFF);
        
		return size;
    }

    inline bool translatePack(BufferType const & buf, std::size_t startPos, std::size_t bufferSize) 
	{ 
		return !m_msg ? 
			false : m_msg->ParseFromArray(&buf.at(startPos), bufferSize); 
	}

private:
    inline void encodeHeader(BufferType & buf, unsigned size) const
    {
        buf[0] = static_cast<boost::uint8_t>((size >> 24) & 0xFF);
        buf[1] = static_cast<boost::uint8_t>((size >> 16) & 0xFF);
        buf[2] = static_cast<boost::uint8_t>((size >> 8) & 0xFF);
        buf[3] = static_cast<boost::uint8_t>(size & 0xFF);
    }

    MessagePtr m_msg;

};

/**
 * Helpers
 */

namespace Detail {

template <class InputIterator>
std::string hexCast(InputIterator first, InputIterator last)
{
    std::string hex;
    char buf[16];
    for (; first != last; ++first) {
        std::sprintf(buf, "%02X ", static_cast<unsigned>(*first) & 0xFF);
        hex += buf;
    }
    return hex;
}
template <class SyncedStream, class Translator>
inline typename Translator::MessagePtr getBody(
	Translator & translator,
	SyncedStream stream,
	std::size_t bodySize)
{
	Core::Proto::BufferType body(bodySize);
	typename Translator::MessagePtr message(new typename Translator::MessageType());
	translator.setMessage(message);
	stream->read(body);
	return translator.translatePack(body, 0, body.size()) ? message : typename Translator::MessagePtr();
}

template <class SyncedStream, class Translator>
inline std::size_t getBodySize(
	Translator & translator, 
	SyncedStream stream) 
{
	Core::Proto::BufferType header(Core::Proto::HEADER_SIZE);
	return (stream->read(header) != Core::Proto::HEADER_SIZE) 
				? 0 : translator.translateHeader(header);	
}

/**
 * The Reply-side data processing
 */
template <class SyncedStream, class Translator>
inline typename Translator::MessagePtr getBodyReply(
	Translator & translator,
	SyncedStream stream,
	std::size_t bodySize)
{
	Core::Proto::BufferType body(bodySize);
	typename Translator::MessagePtr message(new typename Translator::MessageType());
	translator.setMessage(message);

	std::size_t readed = 0; 
	std::size_t totalReaded = 0;
	while ((readed = stream->readAtLeast(body, readed, 1)) > 0) 
	{
		if (totalReaded == bodySize)
			break;
		totalReaded += readed;
	}
	return translator.translatePack(body, 0, body.size()) ? message : typename Translator::MessagePtr();
}

template <class SyncedStream, class Translator>
inline std::size_t getBodySizeReply(
	Translator & translator, 
	SyncedStream stream) 
{
	Core::Proto::BufferType header(Core::Proto::HEADER_SIZE);
	return (stream->readAtLeast(header, 0, Core::Proto::HEADER_SIZE) 
				!= Core::Proto::HEADER_SIZE) 
				? 0 : translator.translateHeader(header);	
}

/**
 *
 */
template <class Translator, class SyncedStream>
struct Encoding {

static inline typename Translator::MessagePtr encodeFromStream(Translator & translator, SyncedStream stream) 
{
	typename Translator::MessagePtr message;
	std::size_t const bodySize = 
		Core::Proto::Detail::getBodySize(translator, stream);
	if (bodySize != 0) 
		message = Core::Proto::Detail::getBody(translator, stream, bodySize);
	return message;
}

static inline typename Translator::MessagePtr encodeFromStreamReply(Translator & translator, SyncedStream stream) 
{
	typename Translator::MessagePtr message;
	std::size_t const bodySize = 
		Core::Proto::Detail::getBodySizeReply(translator, stream);
	if (bodySize != 0) 
		message = Core::Proto::Detail::getBodyReply(translator, stream, bodySize);
	return message;
}

};

} // Detail

} } // namespace Core, Proto

#endif

